package ua.senfiron.finder;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import ua.senfiron.finder.database.FinderContract;
import ua.senfiron.finder.database.FinderDbHelper;
import ua.senfiron.finder.searchengine.AbstractSearchEngine;
import ua.senfiron.finder.searchengine.AbstractSearchEngine.SearchEngineListener;
import android.app.Activity;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.SystemClock;
import android.support.v4.app.Fragment;
import android.util.Log;

/**
 * This Fragment manages a background task and retains 
 * itself across configuration changes.
 */
public class SearchTaskFragment extends Fragment {

    /**
     * Callback interface through which the fragment will report the
     * task's progress and results back to the Activity.
     */
    public static interface SearchTaskCallbacks {
        void onProgressUpdate(SearchResultItem... results);
        void onPostExecute(Integer result);
        void onExpectedQuantityChanged(int difference);
    }

    private int mMaxResults = 300;
    private SearchTaskCallbacks mCallbacks;
    private List<AbstractSearchEngine> mSearchEngines = new ArrayList<AbstractSearchEngine>();
    private List<SearchTask> mSearchTasks = new ArrayList<SearchTask>();
    private List<SearchResultItem> mItems = new ArrayList<SearchResultItem>();
    private ExecutorService mDatabaseSingleThreadExecutor = null;
    
    public List<SearchResultItem> getItems()
    {
        return mItems;
    }
    /**
     * Hold a reference to the parent Activity so we can report the
     * task's current progress and results. The Android framework 
     * will pass us a reference to the newly created Activity after 
     * each configuration change.
     */
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        if(activity instanceof SearchTaskCallbacks)
            mCallbacks = (SearchTaskCallbacks) activity;
    }

    /**
     * This method will only be called once when the retained
     * Fragment is first created.
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Retain this fragment across configuration changes.
        setRetainInstance(true);
    }

    public void addSearchEngine(AbstractSearchEngine engine)
    {
        mSearchEngines.add(engine);
    }
    public void clearSearchEngines()
    {
        mSearchEngines.clear();
    }
    public void setMaxResults(int maxResults)
    {
        this.mMaxResults = maxResults;
    }

    public void startSearch(String query)
    {
        for(AbstractSearchEngine engine : mSearchEngines)
        {
            mSearchTasks.add(new SearchTask(engine));
        }
        mDatabaseSingleThreadExecutor = Executors.newSingleThreadExecutor();
        
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
            for (SearchTask searchTask : mSearchTasks) {
                searchTask.executeOnExecutor(SearchTask.THREAD_POOL_EXECUTOR,
                        query);
            }
        else
            for (SearchTask searchTask : mSearchTasks) {
                searchTask.execute(query);
            }
    }
    public void stopSearch()
    {
        for (SearchTask searchTask : mSearchTasks) {
            if(searchTask.getStatus() == AsyncTask.Status.RUNNING)
                searchTask.cancel(true);
        }
        mSearchTasks.clear();
        shutdownAndAwaitTermination(mDatabaseSingleThreadExecutor);
    }

    public boolean isSearchRunning() {
        return !mSearchTasks.isEmpty();
    }
    public int getTotalRequestedQuantity()
    {
        int count = 0;
        for (SearchTask searchTask : mSearchTasks) {
            count += searchTask.mRequestedQuantity;
        }
        return count;
    }
    /**
     * Set the callback to null so we don't accidentally leak the 
     * Activity instance.
     */
    @Override
    public void onDetach() {
        super.onDetach();
        mCallbacks = null;
    }

    public class SearchTask extends AsyncTask<String, SearchResultItem, Integer> {

        private AbstractSearchEngine mEngine;
        //TODO: Load value from settings
        private int mRequestedQuantity = mMaxResults;
        private int mExpectedQuantity = mRequestedQuantity;

        public SearchTask(AbstractSearchEngine engine) {
            this.mEngine = engine;
            mEngine.setExecutor(this);
            mEngine.setListener(new SearchEngineListener() {
                @Override
                public void onSerachResultLoaded(SearchResultItem item) {
                    publishProgress(item);
                }

                @Override
                public void onMaxResultsCountChanged(int count) {
                    mExpectedQuantity = count;
                    // just update pogressbar.max
                    publishProgress((SearchResultItem[])null); 
                }
            });
        }

        @Override
        protected void onPreExecute() {
            if (mCallbacks != null) {
                mCallbacks.onExpectedQuantityChanged(mRequestedQuantity);
            }
        }

        @Override
        protected Integer doInBackground(String... query) {
            mEngine.setQuery(query[0]);
            int count= 0;
            count = mEngine.loadResults(mRequestedQuantity);
            return count;
        }

        @Override
        protected void onProgressUpdate(SearchResultItem... results) {
            if (mCallbacks != null) {
                if(mExpectedQuantity < mRequestedQuantity)
                {
                    int diff = mExpectedQuantity - mRequestedQuantity;
                    mRequestedQuantity = mExpectedQuantity;
                    mCallbacks.onExpectedQuantityChanged(diff);
                }
                if(results != null)
                    mCallbacks.onProgressUpdate(results);
            }
        }

        @Override
        protected void onPostExecute(Integer result) {

            if (mDatabaseSingleThreadExecutor != null) {
                mDatabaseSingleThreadExecutor.execute(new Runnable() {
                    
                    @Override
                    public void run() {
                        insertIntoDB(mEngine.getQuery(), mEngine, mItems);
                    }
                });
            }
            
            if (getTotalRequestedQuantity() == mItems.size()) {
                stopSearch();
                if (mCallbacks != null) {
                    mCallbacks.onPostExecute(mItems.size());
                }
            }
        }
    }

    private void insertIntoDB(String keywords, AbstractSearchEngine engine, List<SearchResultItem> results) {
        Log.d("Database","Starting DB insert");
        Log.d("Database","Keywords: " + keywords);
        Log.d("Database","Engine: " + engine.getEngineName());
        Context context = null;
        //Try to get associated activity three times;
        for (int i = 0; i < 3; i++) {
            context = this.getActivity();
            if (context == null) {
                SystemClock.sleep(1000);
            } else break;
        }
        if (context == null)
            return;
        FinderDbHelper dbHelper = new FinderDbHelper(context);
        SQLiteDatabase db = dbHelper.getWritableDatabase();

        db.beginTransaction();
        try {

            ContentValues keywordsValues = new ContentValues();
            keywordsValues.put(FinderContract.Keywords.COLUMN_KEYWORDS, keywords);
            long KeywordsID = db.insert(FinderContract.Keywords.TABLE_NAME,
                    null,
                    keywordsValues);

            //for(AbstractSearchEngine engine : engines)
            {
                long SearchEngineID = -1;
                Cursor c = db.query(FinderContract.SearchEngine.TABLE_NAME,
                        null,
                        FinderContract.SearchEngine.COLUMN_ENGINE_NAME + " = ?", 
                        new String[] { engine.getEngineName() },
                        null, null, null, "1");

                if (c != null) {
                    if (c.moveToFirst()) {
                        SearchEngineID = c.getLong(0);
                        c.close();
                    }
                }
                //db.insertWithOnConflict doesn't work as expected
                if(SearchEngineID == -1)
                {
                    ContentValues engineValues = new ContentValues();
                    engineValues.put(FinderContract.SearchEngine.COLUMN_ENGINE_NAME, engine.getEngineName());
                    SearchEngineID = db.insert(FinderContract.SearchEngine.TABLE_NAME,
                            null,
                            engineValues);
                }

                for (SearchResultItem item : results) {
                    if (item.getEngineName() == engine.getEngineName()) {

                        ContentValues resultValues = new ContentValues();
                        resultValues.put(FinderContract.SearchResult.COLUMN_TITLE, item.getTitle());
                        resultValues.put(FinderContract.SearchResult.COLUMN_URL, item.getUrl());;
                        resultValues.put(FinderContract.SearchResult.COLUMN_ENGINE_ID, SearchEngineID);
                        resultValues.put(FinderContract.SearchResult.COLUMN_KEYOWORDS_ID, KeywordsID);

                        db.insert(FinderContract.SearchResult.TABLE_NAME,
                                null,
                                resultValues);
                    }
                }
                Log.d("Database","Finished DB insert");

            }
            db.setTransactionSuccessful();
        } finally {
            db.endTransaction();
            db.close();
        }
        
    }
    
    void shutdownAndAwaitTermination(ExecutorService pool) {
        pool.shutdown(); // Disable new tasks from being submitted
        try {
            // Wait a while for existing tasks to terminate
            if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
                pool.shutdownNow(); // Cancel currently executing tasks
                // Wait a while for tasks to respond to being cancelled
                if (!pool.awaitTermination(60, TimeUnit.SECONDS))
                    System.err.println("Pool did not terminate");
            }
        } catch (InterruptedException ie) {
            // (Re-)Cancel if current thread also interrupted
            pool.shutdownNow();
            // Preserve interrupt status
            Thread.currentThread().interrupt();
        }
    }
}